package mongodb

import (
	"encoding/json"
	"fmt"
	"gopkg.in/mgo.v2"
	. "gopkg.in/mgo.v2/bson"
	"log"
	"runtime"
	"spirit/utils"
	"strings"
	"time"
)

//连接池
var pool chan *mgo.Session

//连接字符串
var MongodbAddr, ReplSet string

const (
	TIMEOUT = 300 * time.Second
)

//连接数据库名
var DB string

//[mongodb://][user:pass@]host1[:port1][,host2[:port2],...][/database][?options]
//初始化mongodb连接池大小
func InitMongodbPool(size int, addr string, dbName string) {
	InitMongodbReplPool(size, addr, dbName, "")
}
func InitMongodbReplPool(size int, addr string, dbName, replset string) {
	defer func() {
		if r := recover(); r != nil {
			log.Println("[E]", r)
			for skip := 1; ; skip++ {
				_, file, line, ok := runtime.Caller(skip)
				if !ok {
					break
				}
				go log.Printf("%v,%v\n", file, line)
			}
		}
	}()
	MongodbAddr = addr
	ms := strings.Split(MongodbAddr, ",")
	if replset == "" && len(ms) > 1 {
		replset = "qfws"
	}
	ReplSet = replset
	pool = make(chan *mgo.Session, size)
	DB = dbName
	for i := 0; i < size; i++ {
		sess, err := createConn()
		if err == nil && sess.Ping() == nil {
			pool <- sess
		}
	}
}

//从连接池中取得mongodb连接,懒加载模式
func GetMgoConn(sec ...int) (session *mgo.Session) {
	defer func() {
		if r := recover(); r != nil {
			log.Println("[E]", r)
			for skip := 1; ; skip++ {
				_, file, line, ok := runtime.Caller(skip)
				if !ok {
					break
				}
				go log.Printf("%v,%v\n", file, line)
			}
		}
	}()
	timeout := TIMEOUT
	if len(sec) == 1 && sec[0] > 4 {
		timeout = time.Duration(sec[0]) * time.Second
	}
	select {
	case session = <-pool:
		if session != nil {
			session.SetSocketTimeout(timeout)
			session.SetSyncTimeout(timeout)
		}
		err := session.Ping()
		if err == nil {
			return session
		} else {
			session.Close()
			session, err = createConn()
			if err == nil && session.Ping() == nil {
				session.SetSocketTimeout(timeout)
				session.SetSyncTimeout(timeout)
				return session
			}
			return nil
		}
	case <-time.After(time.Second * 5):
		//超时
		return nil
	}
}

//取session链接
func createConn() (sess *mgo.Session, err error) {
	if ReplSet != "" {
		info := mgo.DialInfo{
			Addrs:          strings.Split(MongodbAddr, ","),
			Timeout:        TIMEOUT,
			ReplicaSetName: ReplSet,
			Direct:         false,
		}
		return mgo.DialWithInfo(&info)
	}
	return mgo.Dial(MongodbAddr)
}

//释放连接，在取得连接后要使用defer DestoryMongoConn()进行释放
func DestoryMongoConn(session *mgo.Session) {
	defer utils.Catch()
	if session != nil {
		session.SetSocketTimeout(TIMEOUT)
		session.SetSyncTimeout(TIMEOUT)
	}
	pool <- session
}

//修改对象
func Update(c string, query interface{}, set interface{}, upsert bool, multi bool) bool {
	sess := GetMgoConn()
	b := true
	if sess != nil {
		defer func() {
			if r := recover(); r != nil {
				b = false
				log.Println("[E]", r)
				for skip := 1; ; skip++ {
					_, file, line, ok := runtime.Caller(skip)
					if !ok {
						break
					}
					go log.Printf("%v,%v\n", file, line)
				}
			}
		}()
		defer DestoryMongoConn(sess)
		coll := sess.DB(DB).C(c)
		var err error
		if upsert {
			_, err = coll.Upsert(ObjToM(query), ObjToM(set))
		} else {
			if multi {
				_, err = coll.UpdateAll(ObjToM(query), ObjToM(set))
			} else {
				err = coll.Update(ObjToM(query), ObjToM(set))
			}
		}
		if nil != err {
			log.Println("updateError", err)
			b = false
		}
	}
	return b
}

//批量修改
func UpdateBulk(c string, querys []interface{}, sets []interface{}, upsert bool, multi bool) {
	sess := GetMgoConn()
	if sess != nil {
		defer func() {
			if r := recover(); r != nil {
				log.Println("[E]", r)
				for skip := 1; ; skip++ {
					_, file, line, ok := runtime.Caller(skip)
					if !ok {
						break
					}
					go log.Printf("%v,%v\n", file, line)
				}
			}
		}()
		defer DestoryMongoConn(sess)
		coll := sess.DB(DB).C(c)
		for k, set := range sets {
			var err error
			if upsert {
				_, err = coll.Upsert(ObjToM(querys[k]), ObjToM(set))
			} else {
				if multi {
					_, err = coll.UpdateAll(ObjToM(querys[k]), ObjToM(set))
				} else {
					err = coll.Update(ObjToM(querys[k]), ObjToM(set))
				}
			}
			if err != nil {
				log.Println("updateBulkError", err.Error())
			}
		}
	}
}

func DelById(c, id string) bool {
	sess := GetMgoConn()
	b := true
	if sess != nil {
		defer func() {
			if r := recover(); r != nil {
				log.Println("[E]", r)
				for skip := 1; ; skip++ {
					_, file, line, ok := runtime.Caller(skip)
					if !ok {
						break
					}
					go log.Printf("%v,%v\n", file, line)
				}
			}
		}()
		defer DestoryMongoConn(sess)
		coll := sess.DB(DB).C(c)
		err := coll.RemoveId(ObjectIdHex(id))
		if nil != err {
			log.Println("DeleteByIDError", err)
			b = false
		}
	}
	return b
}

//删除对象
func Del(c string, query interface{}) bool {
	sess := GetMgoConn()
	b := true
	if sess != nil {
		defer func() {
			if r := recover(); r != nil {
				log.Println("[E]", r)
				for skip := 1; ; skip++ {
					_, file, line, ok := runtime.Caller(skip)
					if !ok {
						break
					}
					go log.Printf("%v,%v\n", file, line)
				}
			}
		}()
		defer DestoryMongoConn(sess)
		coll := sess.DB(DB).C(c)
		_, err := coll.RemoveAll(ObjToM(query))
		if nil != err {
			log.Println("CountError", err)
			b = false
		}
	}
	return b
}

//批量插入
func SaveBulk(c string, doc ...map[string]interface{}) bool {
	sess := GetMgoConn()
	b := true
	if sess != nil {
		defer func() {
			b = false
			if r := recover(); r != nil {
				log.Println("[E]", r)
				for skip := 1; ; skip++ {
					_, file, line, ok := runtime.Caller(skip)
					if !ok {
						break
					}
					go log.Printf("%v,%v\n", file, line)
				}
			}
		}()
		defer DestoryMongoConn(sess)
		coll := sess.DB(DB).C(c)
		bulk := coll.Bulk()
		for _, v := range doc {
			bulk.Insert(v)
		}
		_, err := bulk.Run()
		if nil != err {
			log.Println("BulkError", err)
			b = false
		}
	} else {
		b = false
	}
	return b
}

//保存对象
func Save(c string, doc interface{}) string {
	sess := GetMgoConn()
	if sess != nil {
		defer func() {
			if r := recover(); r != nil {
				log.Println("[E]", r)
				for skip := 1; ; skip++ {
					_, file, line, ok := runtime.Caller(skip)
					if !ok {
						break
					}
					go log.Printf("%v,%v\n", file, line)
				}
			}
		}()
		defer DestoryMongoConn(sess)
		coll := sess.DB(DB).C(c)
		obj := ObjToM(doc)
		id := NewObjectId()
		(*obj)["_id"] = id
		err := coll.Insert(obj)
		//log.Println(obj)
		if nil != err {
			log.Println("SaveError", err)
			return ""
		}
		return (strings.Split(fmt.Sprintf("%s", id), `"`)[1])
	}
	return ""
}

//统计
func Count(c string, query interface{}) int {
	sess := GetMgoConn()
	var n int = 0
	if sess != nil {
		defer func() {
			if r := recover(); r != nil {
				log.Println("[E]", r)
				for skip := 1; ; skip++ {
					_, file, line, ok := runtime.Caller(skip)
					if !ok {
						break
					}
					go log.Printf("%v,%v\n", file, line)
				}
			}
		}()
		defer DestoryMongoConn(sess)
		coll := sess.DB(DB).C(c)
		var err error
		n, err = coll.Find(ObjToM(query)).Count()
		if nil != err {
			log.Println("CountError", err)
		}
	}
	return n
}

//查询单条对象
func FindOne(c string, query interface{}) *map[string]interface{} {
	return FindOneByField(c, query, nil)
}

//查询单条对象
func FindOneByField(c string, query interface{}, fields interface{}) *map[string]interface{} {
	defer func() {
		if r := recover(); r != nil {
			log.Println("[E]", r)
			for skip := 1; ; skip++ {
				_, file, line, ok := runtime.Caller(skip)
				if !ok {
					break
				}
				go log.Printf("%v,%v\n", file, line)
			}
		}
	}()
	res := Find(c, query, nil, fields, true, -1, -1)
	if nil != res && len(*res) > 0 {
		return &((*res)[0])
	}
	return nil
}

//查询单条对象
func FindById(c string, query string, fields interface{}) *map[string]interface{} {
	sess := GetMgoConn()
	var res map[string]interface{}
	if sess != nil {
		defer func() {
			if r := recover(); r != nil {
				log.Println("[E]", r)
				for skip := 1; ; skip++ {
					_, file, line, ok := runtime.Caller(skip)
					if !ok {
						break
					}
					go log.Printf("%v,%v\n", file, line)
				}
			}
		}()
		defer DestoryMongoConn(sess)
		res = make(map[string]interface{})
		coll := sess.DB(DB).C(c)
		var err error
		err = coll.FindId(ObjectIdHex(query)).Select(ObjToOth(fields)).One(&res)
		if nil != err {
			log.Println("FindByIdError", err)
		}
	}
	return &res
}

//底层查询方法
func Find(c string, query interface{}, order interface{}, fields interface{}, single bool, start int, limit int) *[]map[string]interface{} {
	sess := GetMgoConn()
	var res []map[string]interface{}
	if sess != nil {
		defer func() {
			if r := recover(); r != nil {
				log.Println("[E]", r)
				for skip := 1; ; skip++ {
					_, file, line, ok := runtime.Caller(skip)
					if !ok {
						break
					}
					go log.Printf("%v,%v\n", file, line)
				}
			}
		}()
		defer DestoryMongoConn(sess)
		res = make([]map[string]interface{}, 1)
		coll := sess.DB(DB).C(c)
		var err error
		if single {
			err = coll.Find(ObjToM(query)).Select(ObjToOth(fields)).One(&res[0])
		} else if start > -1 {
			err = coll.Find(ObjToM(query)).Select(ObjToOth(fields)).Sort(ObjToArr(order)...).Skip(start).Limit(limit).All(&res)
		} else {
			err = coll.Find(ObjToM(query)).Select(ObjToOth(fields)).Sort(ObjToArr(order)...).All(&res)
		}
		if nil != err {
			log.Println("FindError", err)
		}
	}
	return &res
}

//对象转数组
func ObjToArr(obj interface{}) []string {
	if s, ok := obj.(string); ok {
		if strings.ContainsAny(s, "{") {
			//暂时简单支持此种写法
			var temp = make(M)
			var str = []string{}
			json.Unmarshal([]byte(s), &temp)
			for k, v := range temp {
				m := utils.IntAll(v)
				if m > 0 {
					str = append(str, k)
				} else {
					str = append(str, "-"+k)
				}
			}
			return str
		} else {
			return strings.Split(s, ",")
		}
	} else if s1, ok1 := obj.([]string); ok1 {
		return s1
	} else {
		return []string{}
	}
}

func ObjToOth(query interface{}) *M {
	return ObjToMQ(query, false)
}
func ObjToM(query interface{}) *M {
	return ObjToMQ(query, true)
}

//obj(string,M)转M,查询用到
func ObjToMQ(query interface{}, isQuery bool) *M {
	data := make(M)
	defer func() {
		if r := recover(); r != nil {
			log.Println("[E]", r)
			for skip := 1; ; skip++ {
				_, file, line, ok := runtime.Caller(skip)
				if !ok {
					break
				}
				go log.Printf("%v,%v\n", file, line)
			}
		}
	}()
	if s2, ok2 := query.(*map[string]interface{}); ok2 {
		data = M(*s2)
	} else if s3, ok3 := query.(*M); ok3 {
		return s3
	} else if s, ok := query.(string); ok {
		json.Unmarshal([]byte(strings.Replace(s, "'", "\"", -1)), &data)
		if ss, oks := data["_id"]; oks && isQuery {
			switch ss.(type) {
			case string:
				data["_id"] = ObjectIdHex(ss.(string))
			case map[string]interface{}:
				tmp := ss.(map[string]interface{})
				for k, v := range tmp {
					tmp[k] = ObjectIdHex(v.(string))
				}
				data["_id"] = tmp
			}

		}
	} else if s1, ok1 := query.(map[string]interface{}); ok1 {
		data = s1
	} else if s4, ok4 := query.(M); ok4 {
		data = s4
	} else {
		data = nil
	}
	return &data
}
